/****************************************************************************
*  Arthea Server by R. Jennings (2007-2008)   http://arthea.googlecode.com/ *
*  By using this code you comply with the Artistic and GPLv2 Licenses.      *
****************************************************************************/


using System;
using System.Collections.Generic;
using Arthea.Abilities;
using Arthea.Affects;
using Arthea.Affects.Enums;
using Arthea.Connections.Players;
using Arthea.Continents.Areas.Characters;
using Arthea.Continents.Areas.Characters.Enums;
using Arthea.Continents.Areas.Rooms;
using Arthea.Continents.Areas.Rooms.Enums;
using Arthea.Continents.Areas.Rooms.Exits;
using Arthea.Globals;
using Arthea.Util;

namespace Arthea
{
    /// <summary>
    /// Implementation of combat.
    /// </summary>
    public struct Combat
    {
        private static readonly List<Character> fighting = new List<Character>();


        // [rgn] Public Methods (6)

        /// <summary>
        /// Causes damage between characters.
        /// </summary>
        /// <param name="skill">The skill.</param>
        /// <param name="killer">The killer.</param>
        /// <param name="victim">The victim.</param>
        /// <param name="dam">The dam.</param>
        public static void Damage(Ability skill, Character killer, Character victim, int dam)
        {
            if (victim.Position == Position.Dead)
                return;

            if (victim.Fighting == null)
                victim.Fighting = killer;

            if (killer.Fighting == null)
                killer.Fighting = victim;

            foreach (Affect aff in victim.Affects)
            {
                if (aff.Flag == AffectFlags.Sanctuary)
                    dam /= 2;
            }

            DamMessage(skill, killer, victim, dam);

            victim.Hit -= dam;

            if (victim.Hit <= 0)
            {
                killer.Act(null, victim, Act.ToChar, "$N is dead!");
                killer.Act(null, victim, Act.ToVictim, "$n has killed you!");
                killer.Act(null, victim, Act.ToRoom, "$n has killed $N!");

                killer.Fighting = null;
                victim.Fighting = null;

                if (victim is Player)
                {
                    victim.Room = Lists.Rooms[Room.Limbo];
                }
                else
                {
                    victim.Release();
                }

                if (killer is Player)
                {
                    Player p = killer as Player;
                    int xp = CalcExp(p, victim);
                    p.Experience += xp;
                    p.WriteLine("You gain {0} experience points!", xp);
                    if (p.CanGainLevel())
                    {
                        p.WriteLine("You can gain another level.");
                    }
                }
            }
        }

        /// <summary>
        /// The damage message.
        /// </summary>
        /// <param name="skill">The skill.</param>
        /// <param name="killer">The killer.</param>
        /// <param name="victim">The victim.</param>
        /// <param name="dam">The dam.</param>
        public static void DamMessage(Ability skill, Character killer, Character victim, int dam)
        {
            int damp = (dam*100)/victim.MaxHit;
            string vs, vp, punc;

            if (damp <= 0)
            {
                vs = "miss";
                vp = "misses";
            }
            else if (damp <= 2)
            {
                vs = "bruise";
                vp = "bruises";
            }
            else if (damp <= 5)
            {
                vs = "scratch";
                vp = "scratches";
            }
            else if (damp <= 9)
            {
                vs = "hit";
                vp = "hits";
            }
            else if (damp <= 13)
            {
                vs = "scar";
                vp = "scars";
            }
            else if (damp <= 18)
            {
                vs = "injure";
                vp = "injures";
            }
            else if (damp <= 23)
            {
                vs = "wound";
                vp = "wounds";
            }
            else if (damp <= 28)
            {
                vs = "maul";
                vp = "mauls";
            }
            else if (damp <= 34)
            {
                vs = "DECIMATE";
                vp = "DECIMATES";
            }
            else if (damp <= 40)
            {
                vs = "DEVASTATE";
                vp = "DEVASTATES";
            }
            else if (damp <= 46)
            {
                vs = "OBLITERATE";
                vp = "OBLITERATES";
            }
            else if (damp <= 53)
            {
                vs = "MUTILATE";
                vp = "MUTILATES";
            }
            else if (damp <= 60)
            {
                vs = "DISEMBOWEL";
                vp = "DISEMBOWELS";
            }
            else if (damp <= 67)
            {
                vs = "*** DEMOLISH ***";
                vp = "*** DEMOLISHES ***";
            }
            else if (damp <= 75)
            {
                vs = "-=- PULVERIZE -=-";
                vp = "-=- PULVERIZES -=-";
            }
            else if (damp <= 83)
            {
                vs = "<-&-> WASTE <-&->";
                vp = "<-&-> WASTES <-&->";
            }
            else if (damp <= 91)
            {
                vs = "<::><::> DISFIGURE <::><::>";
                vp = "<::><::> DISFIGURES <::><::>";
            }
            else
            {
                vs = "inflict UNIMAGINABLE PAIN on";
                vp = "inflicts UNIMAGINABLE PAIN on";
            }

            punc = damp < 10
                       ? "."
                       : damp < 30
                             ? "!"
                             : damp < 45
                                   ? "!!"
                                   :
                                       damp < 60 ? "!!!" : damp < 75 ? "!!!!" : damp < 90 ? "!!!!!!" : "!!!!!!!!";

            if (skill == null)
            {
                if (killer == victim)
                {
                    killer.Act(null, null, Act.ToRoom, "$n {0} $m{1}", vp, punc);
                    killer.Act(null, null, Act.ToChar, "You {0} yourself{1}", vs, punc);
                }
                else
                {
                    killer.Act(null, victim, Act.ToChar, "You {0} $N{1}", vs, punc);
                    killer.Act(null, victim, Act.ToVictim, "$n {0} you{1}", vp, punc);
                    killer.Act(null, victim, Act.ToRoom, "$n {0} $N{1}", vp, punc);
                }
            }
            else
            {
                if (killer == victim)
                {
                    killer.Act(null, null, Act.ToRoom, "$n's {0} {1} $m{2}", skill.DamageNoun, vp, punc);
                    killer.Act(null, null, Act.ToChar, "Your {0} {1} yourself{2}", skill.DamageNoun, vp, punc);
                }
                else
                {
                    killer.Act(null, victim, Act.ToChar, "Your {0} {1} $N{2}", skill.DamageNoun, vp, punc);
                    killer.Act(null, victim, Act.ToVictim, "$n's {0} {1} you{2}", skill.DamageNoun, vp, punc);
                    killer.Act(null, victim, Act.ToRoom, "$n's {0} {1} $N{2}", skill.DamageNoun, vp, punc);
                }
            }
        }

        /// <summary>
        /// Simulates rolling of dice
        /// </summary>
        /// <param name="rolls">the number of die to roll</param>
        /// <param name="dieSize">the number of faces on a die</param>
        /// <returns>the sum of die rolls</returns>
        public static int Dice(int rolls, int dieSize)
        {
            int idice;
            int sum;

            switch (dieSize)
            {
                case 0:
                    return 0;
                case 1:
                    return rolls;
            }

            for (idice = 0, sum = 0; idice < rolls; idice++)
                sum += Randomizer.Next(1, dieSize);

            return sum;
        }

        /// <summary>
        /// Multis the hit.
        /// </summary>
        /// <param name="killer">The killer.</param>
        /// <param name="victim">The victim.</param>
        public static void MultiHit(Character killer, Character victim)
        {
            OneHit(killer, victim);
        }

        /// <summary>
        /// Ones the hit.
        /// </summary>
        /// <param name="killer">The killer.</param>
        /// <param name="victim">The victim.</param>
        public static void OneHit(Character killer, Character victim)
        {
            int ac = 10 + victim.Armor;

            foreach (Affect aff in victim.Affects)
            {
                if (aff.What == AffectType.AC)
                    ac += aff.Modifier;
            }

            int roll = Randomizer.Next(20);

            if (killer is Player)
            {
                roll *= (killer as Player).Class.BaseAttackMod;
                roll /= 100;
            }

            if (roll < ac)
            {
                killer.Act(null, victim, Act.ToChar, "You miss $N.");
                killer.Act(null, victim, Act.ToVictim, "$n misses you.");
                killer.Act(null, victim, Act.ToRoom, "$n misses $N.");
                return;
            }

            int dam = Randomizer.Next(killer.Level/3, killer.Level);

            Damage(null, killer, victim, dam);
        }

        /// <summary>
        /// Updates this instance.
        /// </summary>
        public static void Update()
        {
            if (fighting.Count == 0)
            {
                return;
            }

            // leading line
            foreach (Character ch in fighting)
            {
                if (ch is Player)
                    ch.WriteLine();
            }

            for (int i = 0; i < fighting.Count;)
            {
                Character ch = fighting[i];

                if (ch.Fighting == null)
                {
                    fighting.RemoveAt(i);
                    continue;
                }
                if (ch.Room != ch.Fighting.Room)
                {
                    ch.Fighting = null;
                    continue;
                }

                if (!(ch is Player) && ch.Flags.Has(ActFlags.Wimpy) &&
                    ch.Hit < (ch.MaxHit/5))
                {
                    Direction rand = (Direction) Randomizer.Next(0, Enum.GetValues(typeof (Direction)).Length);

                    if (ch.Room.Exits[rand] != null)
                    {
                        ch.Act(null, null, Act.ToRoom, "$n flees to the {0}!", rand);

                        ch.Room = ch.Room.Exits[rand].ToRoom;

                        ch.Act(null, null, Act.ToRoom, "$n arrives from the {0}, looking frazzled.", Exit.Reverse(rand));

                        fighting.RemoveAt(i);
                        continue;
                    }
                    else
                    {
                        ch.Act(null, null, Act.ToRoom, "$n tries to flee but fails!");
                    }
                }
                MultiHit(ch, ch.Fighting);
                i++;
            }
        }

        // [rgn] Private Methods (1)

        private static int CalcExp(BaseCharacter killer, BaseCharacter victim)
        {
            int levelRange = victim.Level - killer.Level;
            int xp;

            switch (levelRange)
            {
                default:
                    xp = 0;
                    break;
                case -9:
                    xp = 1;
                    break;
                case -8:
                    xp = 2;
                    break;
                case -7:
                    xp = 5;
                    break;
                case -6:
                    xp = 9;
                    break;
                case -5:
                    xp = 11;
                    break;
                case -4:
                    xp = 22;
                    break;
                case -3:
                    xp = 33;
                    break;
                case -2:
                    xp = 50;
                    break;
                case -1:
                    xp = 66;
                    break;
                case 0:
                    xp = 83;
                    break;
                case 1:
                    xp = 99;
                    break;
                case 2:
                    xp = 121;
                    break;
                case 3:
                    xp = 143;
                    break;
                case 4:
                    xp = 165;
                    break;
            }

            if (levelRange > 4)
                xp = 160 + 20*(levelRange - 4);

            return Randomizer.Next(xp*3/4, xp*5/4);
        }

        /// <summary>
        /// Removes the specified ch.
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <returns></returns>
        public static bool Remove(Character ch)
        {
            return fighting.Remove(ch);
        }

        /// <summary>
        /// Adds the specified ch.
        /// </summary>
        /// <param name="ch">The ch.</param>
        public static void Add(Character ch)
        {
            fighting.Add(ch);
        }
    }
}